/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.eclipse.ui.views;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;

import net.sourceforge.pmd.eclipse.runtime.builder.MarkerUtil;
import net.sourceforge.pmd.eclipse.ui.model.AbstractPMDRecord;
import net.sourceforge.pmd.eclipse.ui.model.FileRecord;
import net.sourceforge.pmd.eclipse.ui.model.PackageRecord;
import net.sourceforge.pmd.eclipse.ui.model.ProjectRecord;
import net.sourceforge.pmd.eclipse.ui.model.RootRecord;

/**
 * As chunks of code originally found in the ViolationOverviewContentProvider,
 * the ChangeEvaluator aggregates the same functionality while using formal
 * ChangeRecord instances instead of List triplets.
 * 
 * @author Brian Remedios
 */
public class ChangeEvaluator {

    private final RootRecord root;

    public ChangeEvaluator(RootRecord theRoot) {
        root = theRoot;
    }

    public ChangeRecord<AbstractPMDRecord> changeRecordFor(IResourceChangeEvent event) {

        List<IMarkerDelta> markerDeltas = MarkerUtil.markerDeltasIn(event);

        // first we get a List of changes to Files and Projects so we won't be
        // updating everything
        List<IResource> changedFiles = new ArrayList<>();
        List<IProject> changedProjects = new ArrayList<>();
        for (IMarkerDelta markerDelta : markerDeltas) {
            IResource resource = markerDelta.getResource();
            IProject project = resource.getProject();

            // the lists should not contain Projects or Resources twice
            if (!changedFiles.contains(resource)) {
                changedFiles.add(resource);
                // LOG.debug("Resource " + resource.getName() + " has changed");
            }

            if (!changedProjects.contains(project)) {
                changedProjects.add(project);
                // LOG.debug("Project " + project.getName() + " has changed");
            }
        }

        // we can add, change, or remove Resources
        // all the changes are given to the viewer later

        ChangeRecord<AbstractPMDRecord> changeRec = new ChangeRecord<>();

        // we go through the changed Projects
        for (IProject project : changedProjects) {
            // LOG.debug("Processing changes for project " + project.getName());
            ProjectRecord projectRec = (ProjectRecord) root.findResource(project);

            // if the Project is closed or deleted, we also delete it from the
            // Model and go on
            if (!(project.isOpen() && project.isAccessible())) {
                // LOG.debug("The project is not open or not accessible. Remove
                // it");
                List<AbstractPMDRecord>[] array = updateFiles(project, changedFiles);
                changeRec.removed(array[1]);
                root.removeResource(project);
            } else if (projectRec == null) {
                // if we couldn't find the Project then it has to be new
                // LOG.debug("Cannot find a project record for it. Add it.");
                projectRec = (ProjectRecord) root.addResource(project);
            }

            // then we can update the Files for the new or updated Project
            List<AbstractPMDRecord>[] array = updateFiles(project, changedFiles);
            changeRec.added(array[0]);
            changeRec.removed(array[1]);
            changeRec.changed(array[2]);
        }

        // the additions, removals and changes are given to the viewer
        // so that it can update itself
        // updating the table MUST be in sync
        // this.treeViewer.getControl().getDisplay().syncExec(new Runnable() {
        // public void run() {
        // updateViewer(additions, removals, changes);
        // }
        // });

        return changeRec;
    }

    /**
     * Updates the Files for a given Project
     *
     * @param project
     * @param changedFiles,
     *            a List of all changed Files
     * @return an List of Lists containing additions [0], removals [1] and
     *         changes [2] (Array-Position in Brackets)
     */
    private List<AbstractPMDRecord>[] updateFiles(IProject project, List<IResource> changedFiles) {
        // TODO use ChangeRecord
        List<AbstractPMDRecord> additions = new ArrayList<>();
        List<AbstractPMDRecord> removals = new ArrayList<>();
        List<AbstractPMDRecord> changes = new ArrayList<>();
        List<AbstractPMDRecord>[] updatedFiles = new List[] { additions, removals, changes };

        // we search for the ProjectRecord to the Project
        // if it doesn't exist, we return nothing
        ProjectRecord projectRec = (ProjectRecord) root.findResource(project);

        // we got through all files
        if (projectRec != null && project.isAccessible()) {
            updatedFiles = searchProjectForModifications(projectRec, changedFiles);
        } else if (projectRec != null) {
            // if the project is deleted or closed
            List<AbstractPMDRecord> packages = projectRec.getChildrenAsList();
            // ... we add all Packages to the removals so they are not shown
            // anymore
            removals.addAll(packages);
            for (int k = 0; k < packages.size(); k++) {
                PackageRecord packageRec = (PackageRecord) packages.get(k);
                removals.addAll(packageRec.getChildrenAsList());
            }
            updatedFiles = new List[] { additions, removals, changes };
        }

        return updatedFiles;
    }

    /**
     * Analyzes the modification inside a single project and compute the list of
     * additions, updates and removals.
     *
     * @param projectRec
     * @param changedFiles
     * @return
     */
    private static List<AbstractPMDRecord>[] searchProjectForModifications(ProjectRecord projectRec,
            List<IResource> changedFiles) {

        // TODO use ChangeRecord
        List<AbstractPMDRecord> additions = new ArrayList<>();
        List<AbstractPMDRecord> removals = new ArrayList<>();
        List<AbstractPMDRecord> changes = new ArrayList<>();
        IProject project = (IProject) projectRec.getResource();

        // LOG.debug("Analyses project " + project.getName());

        for (IResource resource : changedFiles) {
            // LOG.debug("Analyses resource " + resource.getName());

            // ... and first check, if the project is the right one
            if (project.equals(resource.getProject())) {
                AbstractPMDRecord rec = projectRec.findResource(resource);
                if (rec != null && rec.getResourceType() == IResource.FILE) {
                    FileRecord fileRec = (FileRecord) rec;
                    fileRec.updateChildren();
                    if (fileRec.getResource().isAccessible() && fileRec.hasMarkers()) {
                        // LOG.debug("The file has changed");
                        changes.add(fileRec);
                    } else {
                        // LOG.debug("The file has been removed");
                        projectRec.removeResource(fileRec.getResource());
                        removals.add(fileRec);

                        // remove parent if no more markers
                        if (!fileRec.getParent().hasMarkers()) {
                            projectRec.removeResource(fileRec.getParent().getResource());
                            removals.add(fileRec.getParent());
                        }
                    }
                } else if (rec == null) {
                    // LOG.debug("This is a new file.");
                    AbstractPMDRecord fileRec = projectRec.addResource(resource);
                    additions.add(fileRec);
                } else {
                    // LOG.debug("The resource found is not a file! type found :
                    // " + rec.getResourceType());
                }
            } else {
                // LOG.debug("The project resource is not the same! (" +
                // resource.getProject().getName() + ')');
            }
        }

        return new List[] { additions, removals, changes };
    }
}
